package com.gitee.srurl.core.factory;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gitee.srurl.core.annotation.ApiImpl;
import com.gitee.srurl.core.api.*;
import com.gitee.srurl.core.api.impl.disable.BlacklistDisabledStrategy;
import com.gitee.srurl.core.api.impl.duplicate.NoDuplicateDetection;
import com.gitee.srurl.core.config.ShortUrlConfig;
import com.gitee.srurl.core.constant.ApiTypeConstant;
import com.gitee.srurl.core.domain.SucBlacklistDomain;
import com.gitee.srurl.core.dto.DataModel;
import com.gitee.srurl.core.dto.request.GenerateShortUrlDTO;
import com.gitee.srurl.core.exception.ServiceException;
import com.gitee.srurl.core.factory.id.IIdStrategyEnums;
import com.gitee.srurl.core.properties.IdGenProperties;
import com.gitee.srurl.core.service.ISucBlacklistService;
import com.gitee.srurl.core.util.SpringContextUtil;
import com.gitee.srurl.register.DataReportHandle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 短链生成工厂
 *
 * @author zxw
 * @date 2023/4/29 7:26
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ShortUrlFactory {

    private static ShortUrlConfig shortUrlConfig;

    private final IdGenProperties idGenProperties;

    private final ISucBlacklistService blacklistService;

    /**
     * 初始化完成标记
     */
    private static volatile boolean initFinally = false;

    @PostConstruct
    private void init() {

        ConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
                .addUrls(ClasspathHelper.forPackage("com.gitee.srurl.core.api.impl"))
                .addScanners(Scanners.TypesAnnotated);
        Reflections reflections = new Reflections(configurationBuilder);
        // 获取接口实现
        Set<Class<?>> apiImplClasses = reflections.getTypesAnnotatedWith(ApiImpl.class);
        // 转为map key为注解配置的参数
        Map<String, Class<?>> typeApiMap = new HashMap<>();
        for (Class<?> apiImplClass : apiImplClasses) {
            ApiImpl annotation = apiImplClass.getDeclaredAnnotation(ApiImpl.class);
            if (annotation == null) {
                continue;
            }
            String value = annotation.value();
            typeApiMap.put(value, apiImplClass);
        }
        // 初始化配置
        ShortUrlFactory.shortUrlConfig = this.getInitConfig(idGenProperties, typeApiMap);
        initFinally = true;
    }

    private ShortUrlConfig getInitConfig(IdGenProperties idGenProperties,
                                         Map<String, Class<?>> typeApiMap) {
        // 设置配置参数
        ShortUrlConfig shortUrlConfig = new ShortUrlConfig();
        shortUrlConfig.setAlwaysNewUrl(Optional.ofNullable(idGenProperties.getAlwaysNewUrl()).orElse(false));
        // 设置重复检测配置
        if (shortUrlConfig.isAlwaysNewUrl()) {
            // 每次都生成新的url的话 不检测重复 该参数设置失效
            shortUrlConfig.setDuplicateDetection(new NoDuplicateDetection());
        } else {
            String duplicateDetectionType = idGenProperties.getDuplicateDetection();
            duplicateDetectionType = StrUtil.isBlank(duplicateDetectionType) ? ApiTypeConstant.DuplicateDetection.NO : duplicateDetectionType;
            IDuplicateDetection duplicateDetection = this.getInstance(typeApiMap.get(duplicateDetectionType));
            shortUrlConfig.setDuplicateDetection(duplicateDetection);
        }
        // 设置数据存储配置
        IDataStore dataStore = SpringContextUtil.getBean(IDataStore.class);
        shortUrlConfig.setDataStore(dataStore);
        // 设置域名选择器
        String domainSelectorType = idGenProperties.getDomainSelector();
        domainSelectorType = StrUtil.isBlank(domainSelectorType) ? ApiTypeConstant.DomainSelector.DEFAULT : domainSelectorType;
        IDomainSelector domainSelector = this.getInstance(typeApiMap.get(domainSelectorType));
        shortUrlConfig.setDomainSelector(domainSelector);
        // 设置禁止策略 第一个必须是黑名单策略
        List<String> disabledStrategyTypeList = new ArrayList<>(Collections.singleton(ApiTypeConstant.DisableStrategy.BLACKLIST));
        List<String> customerDisabledStrategy = idGenProperties.getDisabledStrategy();
        if (CollectionUtil.isNotEmpty(customerDisabledStrategy)) {
            // 如果用户配置了 移除用户的配置 上面第一个已经添加了
            customerDisabledStrategy.remove(ApiTypeConstant.DisableStrategy.BLACKLIST);
            disabledStrategyTypeList.addAll(customerDisabledStrategy);
        }
        // 生成对应的实例
        List<IDisabledStrategy> disabledStrategyInstanceList = new ArrayList<>();
        for (String disabledStrategyType : disabledStrategyTypeList) {
            Class<?> c = typeApiMap.get(disabledStrategyType);
            if (c == null) {
                log.warn("类型:{},实例获取失败", disabledStrategyType);
                continue;
            }

            disabledStrategyInstanceList.add(this.getInstance(typeApiMap.get(disabledStrategyType)));
        }
        shortUrlConfig.setDisabledStrategy(disabledStrategyInstanceList);
        // 初始化黑名单目录
        this.initDomainBlacklist();
        // 设置配置缓存 文件缓存是必须的
        List<String> configCacheTypeList = new ArrayList<>(Collections.singleton(ApiTypeConstant.ConfigCache.FILE));
        List<String> customerConfigCache = idGenProperties.getConfigCache();
        if (CollectionUtil.isNotEmpty(customerDisabledStrategy)) {
            // 如果用户配置了 移除用户配置的文件配置 上面设置了
            customerConfigCache.remove(ApiTypeConstant.ConfigCache.FILE);
            configCacheTypeList.addAll(customerDisabledStrategy);
        }
        List<IConfigCache> configCacheList = new ArrayList<>();
        for (String configCacheType : configCacheTypeList) {
            Class<?> c = typeApiMap.get(configCacheType);
            if (c == null) {
                log.warn("类型:{},实例获取失败", configCacheType);
                continue;
            }
            IConfigCache configCache = this.getInstance(typeApiMap.get(configCacheType));
            configCacheList.add(configCache);
            // 注册上报监听
            DataReportHandle.registerReportListener(configCache);
        }
        shortUrlConfig.setConfigCache(configCacheList);

        // 初始化id生成器
        String idGenType = SpringContextUtil.getRequiredProperty("short-url.id-gen.use-type.type");
        IIdStrategyEnums idGen = IIdStrategyEnums.getInstanceByType(idGenType);
        if (idGen == null) {
            log.error("id生成类型:{},获取id生成器失败", idGenType);
            throw new NullPointerException("id生成器获取失败！");
        }
        IUniqueIdGenerate idGenerate = idGen.getUniqueIdGenerate(configCacheList);
        shortUrlConfig.setIdGenerate(idGenerate);
        // 启动上报
        DataReportHandle.start();
        return shortUrlConfig;
    }

    /**
     * 初始化域名黑名单
     */
    private void initDomainBlacklist() {
        LambdaQueryWrapper<SucBlacklistDomain> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SucBlacklistDomain::getStatus, "1");
        List<SucBlacklistDomain> blacklistDomainList = blacklistService.list(queryWrapper);
        if (CollectionUtil.isNotEmpty(blacklistDomainList)) {
            Set<String> domainList = blacklistDomainList.stream().map(SucBlacklistDomain::getHost).collect(Collectors.toSet());
            BlacklistDisabledStrategy.updateCache(domainList, true);
        }
    }

    @PreDestroy
    private void destroy() {
        DataReportHandle.stop();
    }

    @SuppressWarnings("unchecked")
    private <T> T getInstance(Class<?> implCls) {

        if (implCls == null) {
            log.info("--->创建实例对象为空！");
            throw new IllegalArgumentException("实例对象为空!");
        }

        try {
            return (T) implCls.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            log.error("创建实例异常:", e);
            throw new RuntimeException(e);
        }
    }

    // ================================= static method ===============================

    /**
     * todo 获取匹配的短url
     *
     * @param url 待匹配的url
     * @return 短url
     */
    public static GenerateShortUrlDTO getMatchShortUrl(String url) {

        if (StringUtils.isBlank(url)) {
            throw new ServiceException("待匹配url不能为空！");
        }

        return null;
    }

    public static ShortUrlConfig getShortUrlConfig() {
        if (!initFinally) {
            throw new IllegalStateException("初始化未完成！");
        }
        return shortUrlConfig;
    }

    /**
     * url续期
     * 虽然叫续期 但是如果新设置的到期时间或者使用次数比原本的短(少)，会更新成新的配置参数
     *
     * @param generateShortUrlDTO 生成url参数
     * @return 是否续期成功
     */
    public static boolean shortUrlRenewal(GenerateShortUrlDTO generateShortUrlDTO) {

        if (!initFinally) {
            throw new IllegalStateException("初始化未完成！");
        }
        // url续期
        IDataStore dataStore = shortUrlConfig.getDataStore();
        DataModel dataModel = DataModel.builder()
                .originUrl(generateShortUrlDTO.getOriginUrl())
                .shortKey(generateShortUrlDTO.getShortKey())
                .domain(generateShortUrlDTO.getShortUrlDomain())
                .times(generateShortUrlDTO.getTimes())
                .expireTime(generateShortUrlDTO.getExpireTime())
                .updateTimes(generateShortUrlDTO.getUpdateTimes())
                .build();

        return dataStore.update(dataModel);
    }
}
